using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using Agile.Common.Reflections;

namespace GeneratorCustomization
{
	/// <summary>
	/// Use this to get data from the customization xml file.
    /// This suffices for most usages but sometimes it's worth creating
    /// a class to implement logic associated with the data.
	/// </summary>
	public static class GeneratorsData
	{
		private static GeneratorsDetails _generatorsData;

		/// <summary>
		/// Gets the generators details data from the xml file.
		/// </summary>
		public static GeneratorsDetails All
		{
			get
			{
				if (_generatorsData == null)
				{
					_generatorsData = new GeneratorsDetails();
					_generatorsData.ReadXml(Assemblies.GetFileStream(Assembly.GetCallingAssembly(), "GeneratorsDetailsData.xml"));
				}
				return _generatorsData;
			}
		}

		/// <summary>
		/// Gets all rows for the specified table from the generator data xml file.
		/// </summary>
		/// <param name="tableName">Table in the GeneratorsDetails dataset to retrieve rows for.</param>
		/// <returns></returns>
		public static DataRowCollection GetDataRowsFor(string tableName)
		{
			return GetDataTable(tableName).Rows;
		}

		/// <summary>
		/// Gets the DataTable (with the given name) from the generator data xml file.
		/// </summary>
		/// <param name="tableName">Name of the table in the GeneratorsDetails dataset to retrieve.</param>
		/// <returns></returns>
		public static DataTable GetDataTable(string tableName)
		{
			return All.Tables[tableName];
		}

        /// <summary>
        /// This method can be used to check if a column is listed in any of the customization tables
        /// that have these columns: Database, Table, Column (can have more but this method only checks these)
        /// Also considers if Database or Table has 'All' listed.
        /// </summary>
        /// <param name="customizationTableName">Name of the customization table</param>
        /// <param name="databaseMatch">Name of the Database the Table is in</param>
        /// <param name="tableMatch">Name of the Table the Column is in</param>
        /// <param name="columnMatch">Name of the Column</param>
        /// <returns></returns>
        public static bool ColumnMatchCheck(string customizationTableName
            , string databaseMatch, string tableMatch, string columnMatch)
        {
            return ColumnMatchCheck(customizationTableName, databaseMatch, tableMatch, columnMatch, true);
        }
        
        private const string databaseColumnName = "Database";
        private const string tableColumnName = "TableName";
        private const string columnColumnName = "Column";

	    /// <summary>
        /// This method can be used to check if a column is listed in any of the customization tables
        /// that have these columns: Database, Table, Column (can have more but this method only checks these)
        /// Also considers if Database or Table has 'All' listed.
        /// </summary>
        /// <param name="customizationTableName">Name of the customization table</param>
        /// <param name="databaseMatch">Name of the Database the Table is in</param>
        /// <param name="tableMatch">Name of the Table the Column is in</param>
        /// <param name="columnMatch">Name of the Column</param>
        /// <param name="includeALLCheck">Set to false if you only want exact matches considered. (i.e. Doesn't check for 'All')</param>
        /// <returns></returns>
        public static bool ColumnMatchCheck(string customizationTableName
            , string databaseMatch, string tableMatch, string columnMatch, bool includeALLCheck)
        {
            // makes sure there is a table with data
            DataTable dataTable = GetDataTable(customizationTableName);
            if (dataTable == null) return false;
            if (dataTable.Rows.Count == 0) return false;
            
            // make sure has the right columns in the table
            CheckDataTableHasRequiredColumnsForColumnMatchOperation(customizationTableName, dataTable);

            foreach (DataRow row in dataTable.Rows)
            {
                string column = row[columnColumnName].ToString();

                // first check if the column is a match. If not then we don't need to check anything else
                if(! column.Equals(columnMatch, StringComparison.InvariantCultureIgnoreCase))
                    continue;
                 // ** the columns names do match, also must be a match on Database and Table
                return TableMatchCheck(dataTable, databaseMatch, tableMatch, includeALLCheck);
            }
            return false;
        }


	    /// <summary>
        /// This method can be used to check if a column is listed in any of the customization tables
        /// that have these columns: Database, Table, Column (can have more but this method only checks these)
        /// Also considers if Database or Table has 'All' listed.
        /// </summary>
        /// <param name="customizationTableName">Name of the customization table</param>
        /// <param name="databaseMatch">Name of the Database the Table is in</param>
        /// <param name="tableMatch">Name of the Table the Column is in</param>
        /// <param name="includeALLCheck">Set to false if you only want exact matches considered. (i.e. Doesn't check for 'All')</param>
        public static bool TableMatchCheck(string customizationTableName
            , string databaseMatch, string tableMatch, bool includeALLCheck)
	    {
            return TableMatchGetRow(customizationTableName, databaseMatch, tableMatch, includeALLCheck) != null;
	    }
        
	    /// <summary>
        /// This method can be used to check if a column is listed in any of the customization tables
        /// that have these columns: Database, Table, Column (can have more but this method only checks these)
        /// Also considers if Database or Table has 'All' listed.
        /// </summary>
        /// <param name="dataTable">data from the customization table</param>
        /// <param name="databaseMatch">Name of the Database the Table is in</param>
        /// <param name="tableMatch">Name of the Table the Column is in</param>
        /// <param name="includeALLCheck">Set to false if you only want exact matches considered. (i.e. Doesn't check for 'All')</param>
        public static bool TableMatchCheck(DataTable dataTable
            , string databaseMatch, string tableMatch, bool includeALLCheck)
	    {
            return TableMatchGetRow(dataTable, databaseMatch, tableMatch, includeALLCheck) != null;
	    }

        /// <summary>
        /// This method can be used to check if a column is listed in any of the customization tables
        /// that have these columns: Database, Table, Column (can have more but this method only checks these)
        /// Also considers if Database or Table has 'All' listed.
        /// </summary>
        /// <param name="customizationTableName">Name of the customization table</param>
        /// <param name="databaseMatch">Name of the Database the Table is in</param>
        /// <param name="tableMatch">Name of the Table the Column is in</param>
        /// <param name="includeALLCheck">Set to false if you only want exact matches considered. (i.e. Doesn't check for 'All')</param>
        public static DataRow TableMatchGetRow(string customizationTableName
            , string databaseMatch, string tableMatch, bool includeALLCheck)
        {
            // makes sure there is a table with data
            DataTable dataTable = GetDataTable(customizationTableName);

            // make sure has the right columns in the table
            CheckDataTableHasRequiredColumnsForTableMatchOperation(customizationTableName, dataTable);

            return TableMatchGetRow(dataTable, databaseMatch, tableMatch, includeALLCheck);
        }

	    /// <summary>
        /// This method can be used to check if a column is listed in any of the customization tables
        /// that have these columns: Database, Table, Column (can have more but this method only checks these)
        /// Also considers if Database or Table has 'All' listed.
        /// </summary>
        /// <param name="dataTable">data from the customization table</param>
        /// <param name="databaseMatch">Name of the Database the Table is in</param>
        /// <param name="tableMatch">Name of the Table the Column is in</param>
        /// <param name="includeALLCheck">Set to false if you only want exact matches considered. (i.e. Doesn't check for 'All')</param>
        public static DataRow TableMatchGetRow(DataTable dataTable
            , string databaseMatch, string tableMatch, bool includeALLCheck)
	    {
	        // makes sure there is a table with data
	        if (dataTable == null) return null;
	        if (dataTable.Rows.Count == 0) return null;
 
            foreach (DataRow row in dataTable.Rows)
            {
                string database = row[databaseColumnName].ToString();
                string table = row[tableColumnName].ToString();
                
                // Check for exact match on both Database and Table
                if (table.Equals(tableMatch, StringComparison.InvariantCultureIgnoreCase)
                    && database.Equals(databaseMatch, StringComparison.InvariantCultureIgnoreCase))
                    return row;

                // the column name is the same but either Database or Table do not match
                // so if NOT including an All check just return false.
                if (!includeALLCheck)
                    continue;

                // then both the Database and Table columns could contain 'All'
                // first check if they are both All
                if (database.Equals("All", StringComparison.InvariantCultureIgnoreCase)
                    && table.Equals("All", StringComparison.InvariantCultureIgnoreCase))
                    return row;

                // if this is all then the other definately isn't so just check for an exact match
                if (database.Equals("All", StringComparison.InvariantCultureIgnoreCase))
                    if (table.Equals(tableMatch, StringComparison.InvariantCultureIgnoreCase))
                        return row;

                // if this is all then the other definately isn't so just check for an exact match
                if (table.Equals("All", StringComparison.InvariantCultureIgnoreCase))
                    if (database.Equals(databaseMatch, StringComparison.InvariantCultureIgnoreCase))
                        return row;
            }

            return null;
	    }

        /// <summary>
        /// throw an exception if the table doesn't ahve the right structure.
        /// </summary>
        private static void CheckDataTableHasRequiredColumnsForColumnMatchOperation(string customizationTableName, DataTable dataTable)
        {
            CheckDataTableHasRequiredColumnsForTableMatchOperation(customizationTableName, dataTable);
            if (!dataTable.Columns.Contains(columnColumnName))
            {
                throw new ApplicationException(string.Format("Invalid 'generator customization data table' [{0}] for use with 'CheckMatchIn' method. Tables must have these columns [{1}, {2} & {3}]"
                                                   , customizationTableName, databaseColumnName, tableColumnName, columnColumnName));
            }
        }

        /// <summary>
        /// throw an exception if the table doesn't ahve the right structure.
        /// </summary>
        private static void CheckDataTableHasRequiredColumnsForTableMatchOperation(string customizationTableName, DataTable dataTable)
        {
            if (!dataTable.Columns.Contains(databaseColumnName)
               || !dataTable.Columns.Contains(tableColumnName))
            {
                throw new ApplicationException(string.Format("Invalid 'generator customization data table' [{0}] for use with 'CheckMatchIn' method. Tables must have these columns [{1}, {2} & {3}]"
                                                   , customizationTableName, databaseColumnName, tableColumnName, columnColumnName));
            }
        }

	    private static List<TableRelationship> tableRelationships;
        /// <summary>
        /// Gets all of the 'TableRelationship records (contained in their own class)
        /// </summary>
        /// <returns></returns>
        public static List<TableRelationship> GetTableRelationships()
        {
            if (tableRelationships == null)
            {
                DataTable partyTables = GetDataTable("TableRelationship");
                tableRelationships = new List<TableRelationship>();
                foreach (DataRow row in partyTables.Rows)
                    tableRelationships.Add(TableRelationship.Build(row));
            }
            return tableRelationships;
        }

        /// <summary>
        /// Gets the mapped name for this field (from the GeneratorCustomization data)
        /// </summary>
        public static string GetMappedNameForVariable(string databaseName, string tableName, string standardName)
        {
            // check if we have a mapped name for the property
            DataTable mapData = GetDataTable("PropertyNameMapping");
            foreach (DataRow row in mapData.Rows)
            {
                if (row["Database"].ToString().Equals(databaseName, StringComparison.InvariantCultureIgnoreCase)
                    && row["TableName"].ToString().Equals(tableName, StringComparison.InvariantCultureIgnoreCase)
                    && row["StandardName"].ToString().Equals(standardName, StringComparison.InvariantCultureIgnoreCase))
                {
                    return row["NewName"].ToString();
                }
            }
            return string.Empty;
        }

	}

}